package com.jzo2o.market.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponDto;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.RedisConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponUseBack;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<String> seizeCouponScript;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private IActivityService activityService;

    @Resource
    private ICouponUseBackService couponUseBackService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Override
    public List<ActivityInfoResDTO> selectCountByActivetyId(Set<Long> set) {
        return this.getBaseMapper().selectCountByActivetyId(set);
    }

    /**
     * 分页查询优惠卷领取记录
     * @param dto
     * @return
     */
    @Override
    public PageResult<CouponInfoResDTO> pageQuery(CouponOperationPageQueryReqDTO dto) {
        if(dto==null||dto.getActivityId()==null){
            throw new BadRequestException("参数异常");
        }
        IPage page = new Page(dto.getPageNo(),dto.getPageSize());
         this.lambdaQuery()
                .eq(Coupon::getActivityId, dto.getActivityId())
                .orderByDesc(Coupon::getUpdateTime)
                .page(page);
        PageResult<CouponInfoResDTO> result = new PageResult<>();
        result.setTotal(page.getTotal());
        result.setPages(page.getPages());
        List<CouponInfoResDTO> records = page.getRecords();
        if(records==null){
            return result;
        }
        result.setList(records);
        return result;
    }

    /**
     * 用户查询自己领取的优惠券
     * @param status
     * @param lastId
     * @return
     */
    @Override
    public List<CouponInfoResDTO> myCoupon(Integer status, Long lastId) {
        Long userId = UserContext.currentUserId();
        List<Coupon> list = this.lambdaQuery()
                .eq(status != null, Coupon::getStatus, status)
                .eq(Coupon::getUserId,userId)
                .orderByDesc(Coupon::getCreateTime)
                .lt(lastId != null, Coupon::getActivityId, lastId)
                .last("limit 10")
                .list();
        List<CouponInfoResDTO> dtos = BeanUtils.copyToList(list, CouponInfoResDTO.class);
        return dtos;
    }

    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        //判断活动是否存在
        ActivityInfoResDTO activity = getActivity(seizeCouponReqDTO.getId());
        if(ObjectUtils.isNull(activity)){
            throw new BadRequestException("活动不存在");
        }
        //判断活动状态是否非法
        Integer currentStatus = activityService.getCurrentStatus(activity);
        if(ObjectUtils.notEqual(currentStatus, ActivityStatusEnum.DISTRIBUTING.getStatus())){
            throw new BadRequestException("活动状态非法");
        }
        //判断时间是否非法
        if(DateUtils.now().isBefore(activity.getDistributeStartTime())||DateUtils.now().isAfter(activity.getDistributeEndTime())){
            throw new BadRequestException("活动时间非法");
        }
        Long activityId=seizeCouponReqDTO.getId();
        Long userId = UserContext.currentUserId();
        long index = activityId % 10;
        List<String> keys = Arrays.asList(
                //key[1]抢券同步队列
                String.format(RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME, index),
                //key[2]资源库存
                String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, index),
                //key[3]抢券成功列表
                String.format(RedisConstants.RedisKey.COUPON_SEIZE_LIST, activityId, index)
        );
        Object result = redisTemplate.execute(seizeCouponScript, keys, activityId, userId);
        if(ObjectUtils.isNull(result)) {
            throw new CommonException("抢券失败");
        }
        Long code = Long.valueOf(result.toString());
        if(code>0){
            return;
        }
        if(code==-1){
            throw new CommonException("每人限领一张");
        }
        if(code==-2||code==-4){
            throw new CommonException("库存不足");
        }
        if(code==-3||code==-5){
            throw new CommonException("抢券失败");
        }
    }

    private ActivityInfoResDTO getActivity(Long id) {
        String key= RedisConstants.RedisKey.ACTIVITY_CACHE_LIST;
        Object o = redisTemplate.opsForValue().get(key);
        if(ObjectUtils.isNull(o)){
            return null;
        }
        List<ActivityInfoResDTO> list = JsonUtils.toList(o.toString(), ActivityInfoResDTO.class);
        ActivityInfoResDTO dto = list.stream().filter(x -> ObjectUtils.equals(x.getId(), id))
                .findFirst().orElse(null);
        return dto;
    }

    /**
     * 获取可用优惠券列表
     * @param totalAmount
     * @return
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {
        List<Coupon> list = this.lambdaQuery()
                .eq(Coupon::getUserId,UserContext.currentUserId())  //获取当前用户的优惠卷
                .gt(Coupon::getValidityTime,DateUtils.now())  //过期时间小于当前时间
                .le(Coupon::getAmountCondition,totalAmount)//满减金额门槛小于总金额
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus()) //未使用
                .list();
        //没有可用优惠卷
        if(CollUtils.isEmpty(list)){
            return CollUtils.emptyList();
        }
        List<AvailableCouponsResDTO> dtoList = list.stream()
                .peek(x -> x.setDiscountAmount(CouponUtils.calDiscountAmount(x, totalAmount))) //设置折扣金额
                .filter(x -> x.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0 && x.getDiscountAmount().compareTo(totalAmount) < 0) //筛选折扣金额大于0并且折扣金额小于商品总金额
                .map(x -> BeanUtils.copyBean(x, AvailableCouponsResDTO.class))   //转为要返回的类型
                .sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
        return dtoList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CouponUseResDTO use(CouponUseReqDTO dto) {
        //1. 判断参数是否为空
        if(ObjectUtils.isNull(dto.getId())||ObjectUtils.isNull(dto.getOrdersId())||ObjectUtils.isNull(dto.getTotalAmount())){
            throw new BadRequestException("非法参数");
        }
        Coupon coupon = this.getById(dto.getId());
        //2. 判断是否我的优惠券
        //2.1 优惠券是否存在
        if(ObjectUtils.isNull(coupon)){
            throw new BadRequestException("非法优惠券");
        }
        //2.2判断是否是我的优惠券
        if(ObjectUtils.notEqual(coupon.getUserId(),UserContext.currentUserId())){
            throw new BadRequestException("非法优惠券");
        }
        //2.3 优惠券是否未使用
        if(ObjectUtils.notEqual(coupon.getStatus(),CouponStatusEnum.NO_USE.getStatus())){
            throw new BadRequestException("非法优惠券");
        }
        //2.4 优惠券是否还未过期
        if(DateUtils.now().isAfter(coupon.getValidityTime())){
            throw new BadRequestException("非法优惠券");
        }
        //2.5 总金额是否大于等于满减金额
        BigDecimal discountAmount = CouponUtils.calDiscountAmount(coupon, dto.getTotalAmount());
        if(dto.getTotalAmount().compareTo(coupon.getAmountCondition())<0){
            throw new BadRequestException("非法优惠券");
        }
        //3. 更新优惠券
        boolean update = this.lambdaUpdate()
                .set(Coupon::getOrdersId, dto.getOrdersId())
                .set(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                .set(Coupon::getUseTime, DateUtils.now())
                .eq(Coupon::getId, dto.getId())
                .update();
        if(!update){
            throw new DBException("核销优惠券失败");
        }
        //4. 新增优惠券核销记录
        CouponWriteOff off = new CouponWriteOff();
        off.setCouponId(dto.getId());
        off.setOrdersId(dto.getOrdersId());
        off.setActivityId(coupon.getActivityId());
        off.setUserId(UserContext.currentUserId());
        off.setWriteOffManName(UserContext.currentUser().getName());
        off.setWriteOffManPhone(coupon.getUserPhone());
        off.setWriteOffTime(DateUtils.now());
        boolean save = couponWriteOffService.save(off);
        if(!save){
            throw new DBException("核销优惠券失败");
        }
        CouponUseResDTO resDTO=new CouponUseResDTO();
        resDTO.setDiscountAmount(discountAmount);
        return resDTO;
    }
    @Autowired
    private ICouponUseBackService useBackService;
    /**
     * 退回优惠券
     * @param dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useBack(CouponUseBackReqDTO dto) {
        Coupon coupon = this.getById(dto.getId());
        if(ObjectUtils.isNull(coupon)){
            throw new BadRequestException("优惠券不存在");
        }
        //添加优惠券退回记录
        CouponUseBack back = new CouponUseBack();
        back.setCouponId(dto.getId());//优惠券id
        back.setUseBackTime(DateUtils.now());//优惠券退回时间
        if(dto.getUserId()!=null) {
            back.setUserId(dto.getUserId());//用户id
        }
        back.setWriteOffTime(coupon.getUseTime());//核销时间
        boolean save = useBackService.save(back);
        if(!save){
            throw new DBException("优惠券退回失败");
        }
        //更新优惠券
        boolean update=false;
        if(coupon.getValidityTime().isAfter(DateUtils.now())) {
            update = this.lambdaUpdate()
                    .set(Coupon::getUseTime, null)
                    .set(Coupon::getOrdersId, null)
                    .set(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                    .eq(Coupon::getId, dto.getId())
                    .update();
        }else{
            update = this.lambdaUpdate()
                    .set(Coupon::getStatus, CouponStatusEnum.INVALID.getStatus())
                    .eq(Coupon::getId, dto.getId())
                    .update();
        }
        if(!update){
            throw new DBException("优惠券退回失败");
        }
        //删除核销记录
        LambdaQueryWrapper<CouponWriteOff> wrapper = Wrappers.<CouponWriteOff>lambdaQuery()
                .eq(CouponWriteOff::getCouponId, dto.getId());
        boolean remove = couponWriteOffService.remove(wrapper);
        if(!remove){
            throw new DBException("优惠券退回失败");
        }
    }

    @Override
    public CouponDto getCouponByOrdersId(Long ordersId) {
        Coupon one = this.lambdaQuery()
                .eq(Coupon::getOrdersId, ordersId.toString())
                .one();
        CouponDto couponDto = BeanUtils.copyBean(one, CouponDto.class);
        return couponDto;
    }
}
